Damp

 

Building your own picture window mp3 player.

 

Author notes: This tutorial is written based on the mp3 player that was custom built for it. You will need download the mp3 player in order to understand some, if not most, part of the tutorial. This tutorial doesn’t explain step by step on what the code does as it’s impractical to comment 900 lines of code.

 

Download Damp

 

*** You may notice differences in the code that is provided to the code in the full script. This is because the development of the script was never stopped even when this tutorial was written.

 

 

Introduction

            Mp3 player scripts have been part and parcel of mIRC scripting since mp3 support was introduced. Since then, it has bred a lot of mp3 players. Most took the form of a dialog enhanced with the much-acclaimed MDX dll, while only a handful took the form of picture window (picwin). However, these picwin players were few and merely lived in the shadow of the dialog based mp3 player. Personally I think the reason behind this circumstance is that picwins are so alien to many of the scripters, especially since dialogs provide such an easy way to make a graphical user interface.

 

            Hopefully, this tutorial will cast some light into the shadow of doubt among the many who have not dared to approach picwins. Picwins may be used for a lot of functions, however the popularity of the mp3 player cannot be denied, and therefore a tutorial on picwin mp3 players is just nice!

 

            This tutorial, however, will not explain line by line what the code does. It will instead suggest a few ways for one to go about the coding. Therefore, some basic mIRC scripting knowledge is required, especially the /draw commands (read /help picture windows or picture windows tutorial by dohcan). This tutorial also uses a few snippets that have been released and can be found at http://www.mircscripts.org/. The snippets include:

 

·        ^Andy        - moving windows without titlebar

·        xiao-wen-zi - $button.text

·        xiao-wen-zi - picwin trackbar

 

            The picwin mp3 player that we are going to build has three main structures:

 

·        The database

·        The objects

·        The play-list

 

And this mp3 player will have the skinning ability.

 

The database

 

            For a script to function well, it will need to have a set of variables that are easily accessible. For the mp3 player I chose to make use of hash tables (read /help hash tables). For this input-output process, I have written the following aliases to do the job:

 

            alias -l v hadd -m damp.cache $1 $2-

alias -l uv if ($hget(damp.cache,$1)) { hdel damp.cache $1 }

 

The above aliases are simple. The command /v <item> <value> allows me to add a variable to the damp.cache table, while the /uv <item> allows me to unset a certain <item> in that table.

 

alias -l rd if ($hget(damp.cache,$1)) { return $ifmatch }

 

What good is a value/variable when you can’t access it? The above alias certainly gets the job done. It’s used as an identifier, $rd(<item>), to provide better code handling throughout the script. You may fancy $id(<item>) since ‘id’ stands for identification, while I used ‘rd’ which means read. ‘r’ is not used because it’s the same as $rand().

 

            Note that all the above aliases are localized to prevent conflicts with other loaded scripts. This is especially true when many scripts uses /v for /voice.

 

            Now that we have the aliases to input and retrieve values from a database, it’s time for us to build the database. This database is simply the platform for our objects to locate their rightful position on the GUI itself. Therefore, the database and the objects are very much linked together.

 

            alias -l button.play if ($isid) { return $rd(button.play) } | else { v button.play $1- }

 

The above alias works in two ways. We will use /button.play <value> to store the value for this object in the database, while to retrieve this object’s value we will use the alias in its identifier form, $button.play.

 

button.play  is an object of the player. Yes, you guessed right, it’s the name for the button that we use to play songs. Other objects in the script will be discussed in the objects section.

 

It’s amazing how much you can achieve with such a simple way of coding. The database may seem simple, but when you look at the full script and its code, you will notice that for each object that is provided in this mp3 player I’ve coded an identical alias. This has it’s own pros and cons.

 

The pros are that you will have neat coding and an easy to follow script. This also makes the mp3 player upgrade friendly. However, the down side is that you will have too many identical aliases. Since the structures of the aliases are so similar, there is another way of doing it: just one alias to do it all!

 

Example:

 

alias –l button {

     if ($isid) { return $rd($1) }

                     else { v $$1- }

}

 

The above alias would mean that we will need to use /button <name> <value> in order to input a value and $button(<name>) to retrieve a value. Notice how I used $$1- when setting the value? In doing so, I make sure that there won’t be any blank data or errors when calling the /v command. This alias seems better, and it greatly reduces the file size, however for this script I have chosen the earlier one simply for explanatory purposes.

 

            So we have got the database going, now let’s move on to the objects.

 

The objects

            What is an object in this script? What’s object oriented scripting? Well, very simple. Objects in this script are those buttons, boxes and trackbars that work independently. You do not need to recode them to suit every new player you will make in the future. You can copy and paste them as they are. So it’s kind of like doing a script without knowing how it gets the result. You just add whatever objects you want and the scripts will work!

 

            In this mp3 player, the functionality of these objects has allowed it to be skin-able! How? Well, we set the value for each needed object of the mp3 player in a skin file. When we run the player, it will look for these values and place the objects accordingly on the GUI.

 

alias -l build.button.play {

     var %cood $rd(button.play)

     if (!%cood) { return }

     tokenize 44 %cood

    drawtext -rcn $w $c3 $button.text(marlett,$5,$1,$2,$3,$4,8)

    dbox1 $1-4

}

 

The above alias is the build object command for the object button.play. In the above alias, the code looks for the values set in the database and later draws it on the GUI. Since the values for button.play are stored in this format: x,y,w,h,fontsize, the retrieved values are tokenized for easier access. The $c3 is the value for our text color while the command /dbox1 $1-4 is the command to draw the button/box on the GUI.

 

In addition to /dbox1, there is the /dbox2 command. Both the commands draw a button/box on the GUI, but each has a different effect!

 

alias -l dbox1 {

       drawline -rn $w $c5 1 $1 $calc($2 + $4 -1) $1 $2 $calc($1 + $3 -1) $2

       drawline -rn $w $c4 1 $1 $calc($2 + $4 -1) $calc($1 + $3 -1) $calc($2 + $4 -1) $calc($1 + $3 -1) $2 

}

 

The above /dbox1 will draw a button that is not pressed by the mouse! To achieve this, we must understand how colors trick our eyes! A square will look 3D with the right border color. A lighter color on the west and north border and a darker one on the east and south border of the box will make the box look as if it is a button that’s not pressed!

 

As you can see from above, we have the $c5 and $c4 representing our border colors. Simply, the $c5 returns the lighter color, highlight as Windows would call it. The $c4 returns the darker color, or border/shadow.

 

alias -l dbox2 {

       drawline -rn $w $c4 1 $1 $calc($2 + $4 -1) $1 $2 $calc($1 + $3 -1) $2

       drawline -rn $w $c5 1 $1 $calc($2 + $4 -1) $calc($1 + $3 -1) $calc($2 + $4 -1) $calc($1 + $3 -1) $2 

}

 

While the un-pressed button is drawn with the lighter color on the west and north borders and the darker color on the east and south borders of the designated box, we simply reverse the order and we get a pressed button! And that’s exactly what /dbox2 does!

 

You have probably noticed the $1 to $4 values in both the /dbox commands. They are basically the x-coordinate, y-coordinate, width and height of the buttons. The drawing requires two /drawline commands. We draw the first line from the southwest corner to the northwest corner and proceed to the northeast corner, and the second line would be drawn from the southwest corner to the southeast corner and meet the first line at the northwest corner.

 

The objects list

Enough of the box drawing, let’s get on with the objects in the player that we will write. Below is a list of objects that the current player supports:

 

box.<name> x,y,w,h,font,fontsize

  • box.kbs

Song kbs box

  • box.khz

Song khz box

  • box.mode

Song mode(mono/stereo) box

  • box.song

Song name box

  • box.time

Song time box

  • box.title

mp3 player titlebar

button.<name> x,y,w,h,fontsize

  • button.eject

Eject song button

  • button.exit

Close mp3 player button

  • button.min

Minimize mp3 player button

  • button.next

Next song button

  • button.pause

Pause song button

  • button.play

Play song button

  • button.playlist

Open playlist button

  • button.prev

Previous song button

  • button.random

Enable/disable random play button

  • button.repeat

Enable/disable repeat play button

  • button.stop

Stop song button

c<N> <RGB value>

  • c1

Background color 1

  • c2

Background color 2

  • c3

Text color

  • c4

Shadow/border color

  • c5

Hi-light color

text.<name> [text]

  • text.kbs

Text of song kbs box

  • text.khz

Text of song khz box

  • text.mode

Text of song mode(mono/stereo) box

  • text.song

Text of song name box

  • text.time

Text of song time box

  • text.title

Text of titlebar

trackbar.<name> x,y,w,h,bar size

  • trackbar.progress

Song progress trackbar

  • trackbar.volume

Volume trackbar

 

As you can see, those are the currently available objects in the player. How do these objects work? That is what we will be looking into!

 

An object will need a build command and a function command. The build command has been discussed earlier, however, not the function command. The function command is simply the command that is triggered when a certain action has been done to the object. Lets take the button.play object for example.

 

Commanding an object through mouse events

Naturally, when someone presses the button.play object, it will play a song. But we need to animate it! We will have a few steps to consider when doing its animation. First, when the button is being pressed, we will need to make it look as if it’s being pressed; but the fact is that it’s just a 2D picwin that the mouse interacts with, so we will use the /dbox2 command as discussed earlier. However, the /splay will not be called because the command has not been confirmed! This leads to the following two important mouse events, the sclick and the uclick (read /help picture windows).

 

Wait a minute, what if the user held the left mouse button down and continued the click from the play button to the button next to it? In this case, we will need to make the play button un-pressed and press down the other. This brings in the mouse event in our menu event!

 

menu @damp {

     mouse: return $mo($mouse.x,$mouse.y)

     sclick: return $sc($mouse.x,$mouse.y)

     uclick: return $uc($mouse.x,$mouse.y)

}

 

So we have assigned each of the mouse events to a command.

 

alias –l sc if ($inrect($1,$2, [ [ $g($button.play) ] ] )) { dbox2 $p($button.play) | h $p($button.play) }

 

The above alias is called when the mouse clicks on something. As you can see, the code is just a part taken from the whole script, but it will do for the following explanation. The $inrect() identifier (read /help picture windows) will ensure that when the mouse click is in the area of  the object button.play it calls the /dbox2 command to draw the pressed button.

 

alias –l uc if ($inrect($1,$2, [ [ $g($button.play) ] ] )) { dbox1 $p($button.play) | h $p($button.play) | cmd.button.play }

 

 

The mouse release event has the above alias. It’s about the same as the /sc but with just two different things. It uses /dbox1 to draw the button, making the user believe that the button pops out from being pressed and also the /cmd.button.play command which contains the /splay command. So it did what the button should do after all!

 

            “Wait!” You say? Why can’t we just call the /cmd.button.play in the /sc alias? Isn’t the object supposed to perform its function when pressed? Why are you making it only perform the function when it’s released?

 

            Before answering your question, let’s look at the /mo alias for the mouse event.

 

alias -l mo  if ($mouse.key & 1) && ($inrect($1,$2 , [ [ $g($button.play) ] ] )) { dbox2 $p($button.play) | h $p($button.play) }

 

The /mo alias only performs when the left button of the mouse is being pressed! This allows the mentioned function of moving a pressed mouse from button to button! So this is why we didn’t call the /cmd.button.play command when the sclick mouse event happened!

 

Now to explain the $g, $p, and h aliases.

 

The data is stored in such a way that it needs some modification when being used in the script. In all the above mouse events we only need the x,y,w,h values, but in the database the values are stored with an extra value or two. So we need to remove them.

 

alias -l g if ($gettok($1,1-4,44)) { return $ifmatch } | else return 0,0,0,0

alias -l p return $replace($g($1),$chr(44),$chr(32))

 

The $g alias removes the excessive values in the data retrieved from the database. Because $inrect() needs to have a value for its x,y,w,h we substitute a no match from the database with a 0,0,0,0 so as not to break the script.

 

The $p alias, on the other hand, changes the commas, $chr(44), into spaces, $chr(32), so that the /dbox command receives its parameter in the x y w h format.

 

alias -l h {

     var %h $rd(history)

     if (!%h) { v history $1- | return }

     if (%h == $1-) { return }

     if ($numtok(%h,32) == 4) { dbox1 %h }

     v history $1-

     dd

}

 

The /h alias is actually a history cache. It caches all of the pressed buttons, and when the mouse moves to another button, it will release the pressed button!

 

            Though we already have a few objects in the player, we need to have the main window. The basic command to make a picture windows is /window –p @damp. Since the mp3 player is built in mIRC, we will only need to show the window in mIRC, but we do not want the mp3 player to be covered by other windows in mIRC. Therefore, we will make the @damp window as a desktop window that is always on top. So /window –phdo @damp will do the job. But we do not want the window’s titlebar, and we do not want it to show in the taskbar. Therefore, we have the following command:

 

 /window -apk0CBdo +deL $w -1 -1 $rd(w) $rd(h)

 

            Now, we have a working GUI. No, you say? Well, as I have discussed earlier, the whole structure of the script is based on the database and objects. Therefore, all objects are identical to the button.play object we have discussed so far; only the cmd.<name> function is different as required. You will need to study the full code I have presented though.

 

The play list

 

            An mp3 player generally plays an mp3 file, so we will need a play-list! Now let’s discuss the ways that a play-list can be done! There are many ways to do a play-list. Most people will rely on the basic method of opening a list window and placing the song filenames into it. So in the list, you will see the full path of the song, which means you will see something like c:\music\mp3\song_name_here.mp3. Well, when you have just a few mp3s, that makes perfect sense and it’s not messy for you to navigate. But what if you have hundreds or thousands of them? It will be really messy and hard to navigate once the file names get very long!

 

            Therefore, the option of using two list windows comes into play. The first one will do what the above window does, but it is hidden! So it’s a cache window! Then the second window will be the “formatted” window. When I say formatted, it means the song details, especially the filename, are being processed for easy navigation. For example, the song may be located at c:\music\mp3\song_name_here.mp3, so the full path of the file will be stored in the cache window while only the song name will be listed in the second window. So the second window is a GUI by itself. (A very basic one I might add.) Though it seems simple, with a little bit of different coding, you could get a different result.

 

            Unintentionally, the format for mp3 info to be displayed seemed to have been standardized to <artist> - <song name>. However, not all files are named in such a way. This brings the ID3 tag into the scene. In the $sound() identifier, there are many properties you can use to get certain information from the mp3 file.

 

Properties: album, title, artist, year, comment, genre, track, length, version, bitrate, vbr, sample, mode, copyright, private, crc

 

A good scripter will know what property to use for the $sound() identifier when making a good play list. Here is a basic description of the properties:

 

Properties

Value returned

Album

Song album

Title

Song title

artist

Singer

length

The total length/time of the song in milliseconds

bitrate

Kbps

sample

Hz

 

The above properties are the properties that are usually used in an mp3 player. You may wish to add more information in your mp3 player, and so the other properties will prove their usefulness.

 

            There’s one mistake here, we are building a picwin mp3 player. But what we have discussed so far about the play list is just how to use the list window to display the song name. Why can’t we make the list window in a picwin as well? Yes we can. It won’t be really hard, you just need the basic knowledge on the /draw commands and some creativity of your own. However, when presenting the information, it’s all the same!

 

Building the play list

 

            In this player I that have coded, I used both the double list windows and a picwin GUI play list. This is because the picwin GUI was a request and I thought it is only fair for me to actually demonstrate how to make a picwin GUI since I claimed it’s very easy to do. However, before we go into detail on how to make a picwin play list, let see what basic coding can we use to make the input-output of a play list.

 

alias -l build.playlist {

     if ($1 == c) { clear @damp.cache }

     if ($1 == +) || ($1 == c) {

       if (!$window(@damp.cache)) { window -lh @damp.cache }

       var %ext $gettok($nopath($longfn($2-)),-1,46)

      %damp.musicdir = $remove($2-,$gettok($2-,-1,92))

      if (!$isfile($2-)) { return File not found! }

      if (%ext == mp3) { aline @damp.cache $shortfn($2-) }

      else { .loadbuf @damp.cache $f($2-)  }

      if ($window(@damp.list)) { ref.playlist }

  }

  if ($1 == -) {

       if ($2 == *) { .clear @damp.cache | return }

       var %i $sline(@damp.list,0)

       if ($window($wl)) { %i = $sline(@damp.cache,0) }

       while (%i) {

         var %l = $sline(@damp.list,%i).ln

         if ($window($wl)) { %l = $sline(@damp.cache,%i).ln }

         if ($window(@damp.list)) { dline @damp.list %l }

         dline @damp.cache %l

        dec %i

       }

    }

}

 

 

Basic play list code will have the option to add or remove songs from it. The above alias shows exactly how it can be done.

 

            Let’s study the ‘+’ parameter first. With the ‘+’ parameter, I have allowed the addition of either a song or a .txt file that contains a list of mp3s. It is not necessary that a play list be stored in a .txt file, so other file types will work too. However, since I have used the /loadbuf command, I am unable to check if all the lines in the file are valid mp3 files. Therefore, the burden is passed on to /cmd.button.play where it will check if the file is an mp3 file!

 

            With the ‘-’ parameter, it will remove the selected file in the list. Remember, the main ingredient for a successful play list is its cache window! So, we must remove the selected lines one by one and make sure that the order of lines in both the cache window and the user interface window is the same. This is because the selected line in the user interface window will be used as a reference to the cache window. If the same line has different files, you will get the wrong song playing!

 

Picwin play list, more than a basic GUI

 

            The above method sounds very easy, and it’s easier to implement on a second list window than a picwin. Now, let us look into the picwin play list GUI that I have built in the script that come along with the player. Note that the player will automatically use a list window as the play list if the commands for a picwin play list is not found in the skin file.

 

            Again, our picwin play list is coded to be skin-able and thus, it holds onto the concept of  database and objects as well. Building a database for this play list is as simple as how we build the database fpr the main interface of the player. It uses the same approach of each objects anchored to a command, /playlist.<name>.  While the building commands also follows the /build.<name> concept. All in all, the hardest part of a picwin play list is the listbox, since the rest are also buttons that we have discussed earlier.

 

            To many, they found that a listbox in picwin is very hard to do. But this is not exactly the case. A listbox consist of a big square with two buttons and a trackbar attached to it by its side. And that’s the basic interface for a listbox. What many had failed to do is adding the text into the big rectangle on the interface. They have fail to get the right coordinate for each new line and others. Now, let us look at the main code I used for our player.

 

alias -l pl.update.list {

     var %cood $rd(pl.list) | if (!%cood) { return }

     tokenize 44 %cood

     tokenize 32 $1 $2 $calc($3 -14) $4 $5-

     var %h $int($calc($height(T,$5,$6) +2)) , %l $int($calc($4 / %h)) , %s $rd(scrollline) , %i 1   

     var %w $calc($3 - $width(T,$5,$6) *2) , %x $calc($1 + $width(T,$5,$6)) , %y $calc($2 + 2)

     drawrect -frn $wl $c2 1 $1-4 | dbox2 $wl $1-4

     if ($calc($line(@damp.cache,0) - %s ) < %l) && ($line(@damp.cache,0) >= %l) {

         %s = $calc($line(@damp.cache,0) - %l +1) | v scrollline %s

     }

     while (%i <= %l) {

         var %v = $nopath($longfn($line(@damp.cache,%s)))

         if (!%v) { inc %i | continue }

         if ($width(%v,$5,$6) >= %w) { %v = $button.text($5,$6,$1,%y,%w,%h,%v)  }

         else { %v = $5 $6 %x %y %w %h %v }

         if (%s == $sline(@damp.cache,1).ln) { drawrect -frn $wl $c5 1 %x %y %w %h  }

         drawtext -rcn $wl $c3 %v

         inc %i

         inc %s

         inc %y %h

    }

    var %p $int($calc($rd(scrollline) / $line(@damp.cache,0) *100))

    v pl.scroll.percent $iif((%p > 95),95,%p)

    update.trackbar.progress pl.scroller

    ddl

}

 

The above alias is abit complex and might need some understanding. But, to put everything in a simple sentence, it’s the $width() and $height() identifiers (read /help picture windows) that’s important.

 

            Before we begin, I should remind you that the $1-$6 parameter we stored in x,y,w,h,font,fontsize format. I minused a 14 pixels from the width because the 14 width was used to make the scroll buttons! Now, we have already got ourselves the x,y,w,h of the area which we can work on. Now, we use the $height() identifier to get the font height and we divide it with the total height of the box in order to find the total line of text we can put in. The total width of the box isn’t so important because we are using /drawtext –c which require us to supply the width and height of the area of which we can draw on. But, I made a few changes to the x,y and h value so that it looks nicer when put together. I call the values that affected the changes constant modifier.

 

Events and lines

 

            Remember the rules that each line we present to a user GUI must match the file in the cache window? This is also the golden rule for picwin play list. But our play list can only accommodate so many lines, so we set a variable call scrollline that will guide the script to start /drawtext from certain line until the limit/maximum line. Whenever the scroll up button is press, the scrollline variable will be decrease by one. And when the scroll down button is press, the scrollline variable will be increase by one. The trackbar  by the side, is tracked by a percentage value as like the progress trackbar and volume trackbar. So, we would do $int($calc(<percentage> / <total lines> * 100)) to get the line which should be set as the scroll line.

 

            The events in for the listbox also follows the main mouse,sclick and uclick. I have added the dclick mouse event to detect a double click on the box that will automatically plays the selected file. But I would like to show you the way to get the line number of where the mouse clicks on!

 

var %h $int($calc($height(T,$5,$6) +2)) , %l $int($calc((%y - $2) / %h + $rd(scrollline)))

 

Again, the $1-$6 values comes from the data stored for the listbox. The %y is the y-coordinate of the mouse. First, we use the $height() identifier get the height of the font, so we know how far apart is each line. The ‘+2’ is a constant modifier. Then, for the line number, we would minus the y-coordinate of the mouse point to the  y-coordinate of the box. Later, it’s divided by the font height to get the line number that the mouse clicked on. But that’s the line number on screen, it’s not the line number that matches the line in the cache window. So, the scrollline variable is added to get the result.

 

            That’s basically how you make a listbox, the mouse events are similar to the main GUI that we have discussed earlier. However, this picwin play list doesn’t             support the ctrl($mouse.key & 2) and shift($mouse.key & 4) click function. After all this discussion, I  believe you should know the objects parameter for the skinning function of a play list.

 

Play list objects

playlist x,y,w,h,font,fontsize

playlist.w

Set play list width. Only require one value.

playlist.adddir

Play list add directory button

playlist.addlist

Play list add list button

playlist.addsong

Play list add song button

playlist.deflist

Playlist default list button

playlist.dellist

Play list delete list button

playlist.delsong

Play list delete song button

playlist.h

Set play list height. Only require one value.

playlist.list

Play list listbox

playlist.savelist

Play list save list button

playlist.title

Play list title

 

 

So far…

            Well, that’s plenty about the play list. So now we will piece everything together!

 

            Step by step, we will:

 

1.      Create a database

2.      Grab information from the database

3.      Start building/drawing the objects on the window

4.      Build the play list

5.      Bind everything together

 

So it’s a simple 5 steps to actually build your own mp3 player.

 

That’s a lot about how to build your own mp3 player in picture windows! However, the player that I have presented here can be improved in a lot of ways! I did mention that the player is skin-able with its object-oriented structure/concept, but it doesn’t support image files! With a little tweaking, it will be able to support image files (hint: /drawpic). Secondly, the play list is not in a picwin, but as I mentioned earlier, a picwin play list is not hard to build at all! Thirdly, the objects are limited. We could actually code an engine of some sort to support unlimited objects! Fourthly, the objects are too static and provide very little customization. More could be done on this.

 

Perhaps you will wish to play around with the player and update it yourself, but for the time being, that’s another story for another time! See you and thank you for reading this tutorial!